<?php

namespace app\common\job;


use app\common\dict\LogLevelDict;
use app\common\model\ScheduledTasks;
use think\facade\Log;
use \think\facade\Queue;
use think\queue\Job;
use Throwable;

/**
 * 定时任务基类
 */
abstract class BaseScheduledTask
{

    protected $queueName = 'scheduledTask';



    /**
     * 任务名称 字母+下划线
     * @var string
     */
    protected string $name = 'base';


    /**
     * 任务标题
     * @var string
     */
    protected string $title = '定时任务';


    /**
     * cron默认表达式
     * @var string
     */
    protected string $cronExpression = '* * * * *';


    /**
     * 任务失败后再次执行的延迟时间，单位秒
     * @var int
     */
    protected $delay = 120;


    /**
     * 任务重试次数
     * @var int
     */
    protected $attempts = 3;


    /**
     * 任务上次执行时间
     * @var int
     */
    protected int $lately_run_time = 0;



    /**
     * 任务模型
     * @var ScheduledTasks
     */
    private $model;


    private $logger;



    protected $isOpen = true;


    /**
     * 执行任务
     * @param array $data
     * @return bool
     */
    abstract function doJob(array $data = []): bool;


    abstract public function getClassName(): string;


    /**
     * 任务执行失败的方法
     * @param $data
     * @return void
     */
    abstract function failed($data): void;


    public function __construct()
    {
        $this->attempts = get_sys_config('task_attempts') ?? 3;
        $this->delay = get_sys_config('task_delay') ?? 120;
        $this->model = new ScheduledTasks();

        $this->logger = Log::channel('queue');


        $this->isOpen = $this->init();
    }


    /**
     * 运行任务
     * @param bool $sync 是否同步执行, 默认异步
     * @param array $data 任务参数
     * @return void
     * @throws \Exception
     */
    public function run(bool $sync = false,array $data = []): void
    {
        if(!$this->isOpen){
            $this->log('任务已禁用，取消执行！', LogLevelDict::WARNING);
            return;
        }
        if($sync){
            $this->doJob($data);
        }else{
            Queue::push($this->getClassName(), $data, $this->queueName);
        }
    }



    public function fire(Job $job, $data = [])
    {
        //调试模式
        if(env('APP_DEBUG', false)){
            $this->log('收到任务参数：' . json_encode($data), LogLevelDict::DEBUG);
        }

        try {
            if($this->doJob($data)){
                $job->delete();
                $this->log('执行成功！');
            }else{
                if($job->attempts() >= $this->attempts){
                    $job->delete();
                    $this->log('执行失败已达最大重试次数，任务取消！', LogLevelDict::ERROR);
                    $this->failed($data);
                }else{
                    // 如果 任务没有被删除，则重新发布任务
                    if(!$job->isDeleted()){
                        $this->log("任务执行失败，正在等待{$this->delay}秒后重试...", LogLevelDict::WARNING);
                        $job->release($this->delay);
                    }
                }
            }
        }catch (Throwable $e){
            $job->delete();
            $this->log($e->getMessage(), LogLevelDict::ERROR);
            //调试模式
            if(env('APP_DEBUG', false)){
                $this->log(json_encode($e->getTrace()), LogLevelDict::DEBUG);
            }
            return false;
        }

        return true;
    }



    /**
     * 记录日志
     * @param string $msg 日志内容
     * @param string $level 日志级别
     */
    protected function log(string $msg, string $level = 'info'): void
    {
        $this->logger->write("[$level]" . $msg, $this->name);
    }


    /**
     * 初始化任务
     * @return bool 是否开启
     * @throws \Exception
     */
    private function init()
    {
        $cron = new \Cron\CronExpression($this->cronExpression);
        $data = $this->model->cache()->where('name', $this->name)->findOrEmpty();
        if ($data->isEmpty()) {
            $this->model->name = $this->name;
            $this->model->cron = $this->cronExpression;
            $this->model->title = $this->title;
            $this->model->lately_run_time = $cron->getPreviousRunDate()->getTimestamp();
            $this->model->next_run_time = $cron->getNextRunDate()->getTimestamp();
            $this->model->class_name = $this->getClassName();
            $this->model->save();
        }

        // 更新任务
        if(!$data->isEmpty() && $this->isOpen && $data->status == 1){
            $this->cronExpression = $data->cron;
            $data->cron = $this->cronExpression;
            $data->lately_run_time = $cron->getPreviousRunDate()->getTimestamp();
            $data->next_run_time = $cron->getNextRunDate()->getTimestamp();
            $data->save();
        }

        return $data->status == 1;
    }


    /**
     * 判断是否到期
     * @return bool
     */
    public function isDue(): bool
    {
        return (new \Cron\CronExpression($this->cronExpression))->isDue();
    }


}